package com.zys.http.processor;

import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.zys.http.constant.HttpEnum;
import com.zys.http.entity.param.ParamProperty;
import com.zys.http.entity.velocity.MethodItem;
import com.zys.http.ui.tree.node.MethodNode;
import jdk.jfr.Description;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.stubindex.KotlinClassShortNameIndex;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author zhou ys
 * @since 2024-05-16
 */
public abstract class AbstractKotlinApiProcessor implements ApiProcessor {

    @Description("包装类")
    protected static final Map<String, Object> BASIC_DATA_TYPE_OBJECT_MAP = new HashMap<>();

    static {
        BASIC_DATA_TYPE_OBJECT_MAP.put("ModelAndView", Collections.emptyMap());
        BASIC_DATA_TYPE_OBJECT_MAP.put("ModelMap", Collections.emptyMap());
        BASIC_DATA_TYPE_OBJECT_MAP.put("Boolean", false);
        BASIC_DATA_TYPE_OBJECT_MAP.put("Byte", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("Int", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("Short", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("Long", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("Float", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("Char", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("Number", 0);
        BASIC_DATA_TYPE_OBJECT_MAP.put("String", "");
        BASIC_DATA_TYPE_OBJECT_MAP.put("JvmType.Object", Collections.emptyMap());
    }

    @Override
    public String packageName(PsiElement psiElement) {
        if (!(psiElement instanceof KtClass ktClass)) {
            return null;
        }
        return ktClass.getContainingKtFile().getPackageFqName().asString();
    }

    @Override
    public String controllerSwagger(PsiElement psiElement) {
        if (!(psiElement instanceof KtClass ktClass)) {
            return "";
        }

        KtModifierList modifierList = ktClass.getModifierList();
        if (Objects.isNull(modifierList)) {
            return "";
        }

        List<HttpEnum.Swagger> swaggers = List.of(HttpEnum.Swagger.API, HttpEnum.Swagger.TAG);

        return getSwagger(modifierList, swaggers);
    }

    @Override
    @SneakyThrows
    public List<MethodNode> methodNodes(PsiElement psiElement, String contextPath) {
        if (!(psiElement instanceof KtClass ktClass)) {
            return Collections.emptyList();
        }
        if (ktClass.isEnum() || ktClass.isInterface() || ktClass.isAnnotation()) {
            return Collections.emptyList();
        }

        String controllerPath = controllerPath(ktClass);
        List<KtNamedFunction> functions = ktClass.getDeclarations().stream().filter(KtNamedFunction.class::isInstance).map(KtNamedFunction.class::cast).toList();
        return functions.stream().map(f -> createMethodNode(f, controllerPath, contextPath)).filter(Objects::nonNull).toList();
    }

    protected abstract MethodNode createMethodNode(KtNamedFunction function, String controllerPath, String contextPath);

    @Override
    public String methodSwagger(PsiElement element) {
        if (Objects.isNull(element)) {
            return "";
        }

        if (!(element instanceof KtNamedFunction function)) {
            return "";
        }
        KtModifierList modifierList = function.getModifierList();
        if (Objects.isNull(modifierList)) {
            return "";
        }
        List<HttpEnum.Swagger> swaggers = List.of(HttpEnum.Swagger.API_OPERATION, HttpEnum.Swagger.OPERATION);

        return getSwagger(modifierList, swaggers);

    }

    private String getSwagger(@NotNull KtModifierList modifierList, List<HttpEnum.Swagger> swaggers) {
        List<KtAnnotationEntry> entries = modifierList.getAnnotationEntries();
        KtAnnotationEntry entry = entries.stream().filter(o -> Objects.nonNull(o.getShortName()))
                .filter(e -> {
                    String anno = e.getShortName().asString();
                    return swaggers.stream().map(HttpEnum.Swagger::getClazz).anyMatch(o -> o.equals(anno) || o.endsWith(anno));
                }).findFirst().orElse(null);
        if (Objects.isNull(entry)) {
            return "";
        }
        Name shortName = entry.getShortName();
        if (Objects.isNull(shortName)) {
            return "";
        }
        String anno = shortName.asString();

        HttpEnum.Swagger operation = swaggers.stream().filter(o -> o.getClazz().equals(anno) || o.getClazz().endsWith(anno)).findFirst().orElse(null);
        if (Objects.isNull(operation)) {
            return "";
        }
        return annotationValue(entry, new String[]{operation.getValue()});
    }

    @Override
    public Map<String, ParamProperty> methodParametersParse(@NotNull PsiElement element, boolean isJsonPretty) {
        if (!(element instanceof KtNamedFunction function)) {
            Map<String, ParamProperty> map = new HashMap<>();
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return map;
        }
        List<KtParameter> parameters = function.getValueParameters();
        if (parameters.isEmpty()) {
            Map<String, ParamProperty> map = new HashMap<>();
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return map;
        }

        Map<String, ParamProperty> map = new HashMap<>();
        for (KtParameter parameter : parameters) {
            parameterParse(parameter, map, isJsonPretty);
        }
        return map;
    }

    protected abstract void parameterParse(@NotNull KtParameter parameter, @NotNull Map<String, ParamProperty> map,
                                           boolean isJsonPretty);

    @Override
    public String annotationValue(PsiElement psiElement, String[] attributeNames) {
        if (!(psiElement instanceof KtAnnotationEntry entry)) {
            return "";
        }
        List<? extends ValueArgument> valueArguments = entry.getValueArguments();
        if (valueArguments.isEmpty()) {
            return "";
        }
        List<KtExpression> expressions = getKtExpressions(attributeNames, valueArguments);
        if (expressions.isEmpty()) {
            return "";
        }
        KtExpression expression = expressions.get(0);
        if (expression instanceof KtStringTemplateExpression) {
            String text = expression.getText();
            return text.startsWith("\"") && text.endsWith("\"") ? text.substring(1, text.length() - 1) : text;
        } else if (expression instanceof KtCollectionLiteralExpression literalExpression) {
            List<KtExpression> innerExpressions = literalExpression.getInnerExpressions();
            String text = innerExpressions.get(0).getText();
            return text.startsWith("\"") && text.endsWith("\"") ? text.substring(1, text.length() - 1) : text;
        } else if (expression instanceof KtDotQualifiedExpression qualifiedExpression) {
            return qualifiedExpression.getText();
        }

        return "";
    }

    @Override
    public List<MethodItem> postmanMethods(PsiElement psiElement, String contextPath) {
        if (!(psiElement instanceof KtClass kt)) {
            return Collections.emptyList();
        }
        String controllerPath = controllerPath(kt);
        List<KtNamedFunction> functions = kt.getDeclarations().stream().filter(KtNamedFunction.class::isInstance).map(KtNamedFunction.class::cast).toList();
        if (functions.isEmpty()) {
            return Collections.emptyList();
        }
        List<MethodItem> methodItems = new ArrayList<>();
        for (KtNamedFunction function : functions) {
            MethodItem methodItem = postmanMethodItem(function, contextPath, controllerPath);
            if (Objects.nonNull(methodItem)) {
                methodItems.add(methodItem);
            }
        }
        return methodItems;
    }

    protected abstract MethodItem postmanMethodItem(KtNamedFunction function, String contextPath, String controllerPath);

    private List<KtExpression> getKtExpressions(String[] attributeNames, List<? extends ValueArgument> valueArguments) {
        List<KtExpression> expressions = new ArrayList<>();
        for (String attributeName : attributeNames) {
            for (ValueArgument argument : valueArguments) {
                ValueArgumentName argumentName = argument.getArgumentName();
                KtExpression expression = argument.getArgumentExpression();
                if (Objects.isNull(argumentName) && "value".equals(attributeName)) {
                    expressions.add(expression);
                }
                if (Objects.isNull(argumentName)) {
                    continue;
                }
                if (argumentName.getAsName().toString().equals(attributeName)) {
                    expressions.add(expression);
                }
            }
        }
        return expressions;
    }

    @Override
    public Object typeDefaultValue(Project project, Cloneable type) {
        if (!(type instanceof KtParameter parameter)) {
            return null;
        }
        return typeDefaultValue(project, parameter, new HashMap<>(), null);
    }

    @SuppressWarnings("deprecation")
    private @Nullable Object typeDefaultValue(Project project, KtParameter parameter, Map<KtTypeReference, Integer> recursionMap, KtTypeReference typeReference) {
        if (Objects.isNull(typeReference) && Objects.isNull(parameter)) {
            return null;
        }

        if (Objects.nonNull(parameter)) {
            typeReference = parameter.getTypeReference();
        }
        if (Objects.isNull(typeReference)) {
            return null;
        }

        String typeText = typeReference.getText();

        // 基元类型对应的包装类
        Object value = BASIC_DATA_TYPE_OBJECT_MAP.get(typeText);
        if (Objects.nonNull(value)) {
            KtExpression defaultValue = parameter.getDefaultValue();
            if (Objects.nonNull(defaultValue)) {
                value = defaultValue.getText();
            }
            return value;
        }

        value = arrayTypeOrCollectionDefaultValue(project, parameter, recursionMap, typeReference);
        if (Objects.nonNull(value)) {
            return value;
        }

        value = dateTypeDefaultValue(parameter);
        if (Objects.nonNull(value)) {
            return value;
        }

        KtUserType userType = (KtUserType) typeReference.getTypeElement();
        if (Objects.isNull(userType)) {
            return null;
        }

        GlobalSearchScope scope = typeReference.getResolveScope();
        // 应该只有一个才对, 没法获取到 fqName
        List<KtClassOrObject> elements = (List<KtClassOrObject>) KotlinClassShortNameIndex.getInstance().get(typeReference.getText(), project, scope);
        if (elements.isEmpty()) {
            return null;
        }

        Map<String, Object> result = new LinkedHashMap<>();
        List<KtClass> ktClasses = elements.stream().filter(KtClass.class::isInstance).map(KtClass.class::cast).toList();
        Integer orDefault = recursionMap.getOrDefault(typeReference, 0);
        if (orDefault < 2) {
            orDefault = orDefault + 1;
            recursionMap.put(typeReference, orDefault);
            for (KtClass ktClass : ktClasses) {
                KtPrimaryConstructor constructor = ktClass.getPrimaryConstructor();
                if (Objects.isNull(constructor)) {
                    continue;
                }
                PsiElement[] children = constructor.getChildren();
                for (PsiElement child : children) {
                    KtParameterList list = (KtParameterList) child;
                    List<KtParameter> parameters = list.getParameters();
                    for (KtParameter ktParameter : parameters) {
                        result.put(ktParameter.getName(), typeDefaultValue(project, ktParameter, recursionMap, null));
                    }
                }
            }
        }

        return result;
    }

    private @Nullable Object arrayTypeOrCollectionDefaultValue(Project project, KtParameter parameter, Map<KtTypeReference, Integer> recursionMap, KtTypeReference typeReference) {
        if (Objects.isNull(parameter) && Objects.isNull(typeReference)) {
            return EMPTY_ARRAY;
        }
        if (Objects.isNull(typeReference)) {
            typeReference = parameter.getTypeReference();
        }
        String typeText = typeReference.getText();
        if (typeText.contains("Map")) {
            return Collections.emptyMap();
        }

        if (typeText.contains("Array") || typeText.contains("List") || typeText.contains("Set")) {
            String arrayCanonicalText = typeText.substring(typeText.indexOf("<") + 1, typeText.indexOf(">"));
            arrayCanonicalText = arrayCanonicalText.endsWith("?") ? arrayCanonicalText.substring(0, arrayCanonicalText.length() - 1) : arrayCanonicalText;
            if ("JvmType.Object".equals(arrayCanonicalText)) {
                return EMPTY_ARRAY;
            }
            // 是否是基元类型或对应包装类型的数组
            Object o = BASIC_DATA_TYPE_OBJECT_MAP.get(arrayCanonicalText);
            if (Objects.nonNull(o)) {
                return List.of(o);
            }
            KtUserType userType = (KtUserType) typeReference.getTypeElement();
            if (Objects.isNull(userType)) {
                return EMPTY_ARRAY;
            }
            KtTypeArgumentList list = userType.getTypeArgumentList();
            if (Objects.isNull(list)) {
                return EMPTY_ARRAY;
            }
            List<KtTypeProjection> arguments = list.getArguments();
            if (arguments.isEmpty()) {
                return EMPTY_ARRAY;
            }
            KtTypeProjection projection = arguments.get(0);
            Object defaultValue = typeDefaultValue(project, null, recursionMap, projection.getTypeReference());

            return Objects.isNull(defaultValue) ? EMPTY_ARRAY : List.of(defaultValue);
        }

        return null;
    }

    @Description("处理日期类型")
    private @Nullable Object dateTypeDefaultValue(KtParameter parameter) {
        if (Objects.isNull(parameter)) {
            return null;
        }
        KtTypeReference typeReference = parameter.getTypeReference();
        if (Objects.isNull(typeReference)) {
            return EMPTY_ARRAY;
        }
        String typeText = typeReference.getText();
        if ("Date".equals(typeText) || "LocalDateTime".equals(typeText)) {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
        }
        if ("LocalDate".equals(typeText)) {
            return new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
        }
        return null;
    }
}
